package erlangtools;

import ise.plugin.nav.NavToolBar;
import ise.plugin.nav.Navigator;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.gjt.sp.jedit.Buffer;
import org.gjt.sp.jedit.EBComponent;
import org.gjt.sp.jedit.EBMessage;
import org.gjt.sp.jedit.View;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.io.VFSManager;
import org.gjt.sp.jedit.search.SearchAndReplace;
import org.gjt.sp.jedit.textarea.Selection;

import errorlist.DefaultErrorSource;
import errorlist.ErrorSource;

public class ErlangTools extends JPanel implements EBComponent {

	private static final long serialVersionUID = 1L;
	
	private View theView;
	private DefaultErrorSource source;
	private JScrollPane functionList;
	
	private ErlangTools me;
	
	private ErlangProjectPanel prj;
	
	private JButton find;
	private JButton dialyze;

	private Navigator nav;

	private JLabel info;

	public void handleMessage(EBMessage arg0) {}
	
	public ErlangTools(View view) {
		if (instance == null) instance = this;
		this.theView = view;
		this.me = this;
		
		setLayout(new BorderLayout());
		source = new DefaultErrorSource("Erlang");
		ErrorSource.registerErrorSource(source);

		JButton compile = new JButton("Compile");
		//this.setPreferredSize(new Dimension(500, 250));
        //this.setVisible(true);
        
        compile.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent arg0) {
				compile();
			}
        	
        });

        JList fl = new JList(new SourceLine[0]);
        functionList = new JScrollPane();
        functionList.getViewport().setView(fl);

        JButton functions = new JButton("Functions");
        add(functionList, BorderLayout.CENTER);
        
		functions.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				functionList();
			}
			
		});
		
		JButton project = new JButton("Project...");
		project.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent arg0) {
				if (prj == null) {
					prj = new ErlangProjectPanel(me);
				}
				prj.setVisible(true);
			}
			
		});
		
		find = new JButton("Find");
		info = new JLabel();
		add(info, BorderLayout.SOUTH);
		find.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent arg0) {
				if (prj != null) {
					info.setText(prj.findClick());
				} else {
					info.setText(findOnCurrentFile());
				}
			}
			
		});
		
		dialyze = new JButton("Dialyze");
		dialyze.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent arg0) {
				dialyze();
			}
			
		});

		JPanel botonera1 = new JPanel();
		JPanel botonera2 = new JPanel();
		botonera1.add(compile);
        botonera1.add(functions);
		botonera1.add(find);
		botonera2.add(project);
		botonera2.add(dialyze);
		
		nav = new Navigator(theView);
		botonera2.add(new NavToolBar(nav));
		
		JPanel botonera = new JPanel(new BorderLayout());
		botonera.add(botonera1, BorderLayout.NORTH);
		botonera.add(botonera2, BorderLayout.SOUTH);
		add(botonera, BorderLayout.NORTH);

		jEdit.getInputHandler().addKeyBinding("A+e A+c", "erlang-tools.compile");
		jEdit.getInputHandler().addKeyBinding("A+e A+l", "erlang-tools.function-list");
		jEdit.getInputHandler().addKeyBinding("A+e A+f", "erlang-tools.find-declaration");
		jEdit.getInputHandler().addKeyBinding("A+e A+b", "erlang-tools.back");
		jEdit.getInputHandler().addKeyBinding("A+e A+v", "erlang-tools.forth");
		jEdit.getInputHandler().addKeyBinding("A+e A+a", "erlang-tools.build-all");
		jEdit.getInputHandler().addKeyBinding("F3", "erlang-tools.find-next");
		jEdit.getInputHandler().addKeyBinding("S+F3", "erlang-tools.find-previous");
	}
	
	protected void dialyze() {
		String fullPath = theView.getBuffer().getPath();
			
		List<CompilerLine> lines = Dialyzer.dialyze(fullPath);
		source.removeFileErrors(fullPath);
		for (CompilerLine line: lines) {
			source.addError(
					line.isWarning()?ErrorSource.WARNING:ErrorSource.ERROR,
							fullPath,
							line.getLineNumber() - 1,
							0,
							0,
							line.getMessage()
			);
		}
	}

	private String findOnCurrentFile() {
		Invocation i = getCurrentInvocation();
		if (i == null) return "not an invocation";
		
		String moduleName = i.getModule();
		if (moduleName != null && !moduleName.equals(getCurrentModule())) {
			return i.toString() + " not found";
		}
		Buffer b = theView.getBuffer();
		List<SourceLine> ls = Parser.parse(b.getName(), b.getText(0, b.getLength()));
		for (SourceLine s: ls) {
			if ((i.getFunction() + "/" + i.getArity()).equals(s.getMessage())) {
				gotoSourceLine(s.getLine(), s.getSourceFile());
				return i.toString() + " found on " + s.getSourceFile();
			}
		}
		return i.toString() + " not found";
	}
	
	public void gotoSourceLine(final int line, String source) {
		final Buffer buffer;
		buffer = jEdit.openFile(theView,source);
		if(buffer == null)
			return;

		VFSManager.runInAWTThread(new Runnable()
		{
			public void run()
			{
				theView.goToBuffer(buffer);

				int start = 0;
				int end = 0;

				int lineNo = line - 1;
				if(lineNo >= 0 && lineNo < buffer.getLineCount())
				{
					start += buffer.getLineStartOffset(lineNo);
					if(end == 0)
						end = buffer.getLineEndOffset(lineNo) - 1;
					else
						end += buffer.getLineStartOffset(lineNo);
				}

				theView.getTextArea().setSelection(
					new Selection.Range(start,end));

				theView.getTextArea().moveCaretPosition(end);
			}
		});
	}
	
	private boolean validChar(char c) {
		return Character.isLetterOrDigit(c) || c == '_' || c == ':';
	}
	
	private boolean atomChar(char c) {
		return Character.isLetterOrDigit(c) || c == '_';
	}
	
	public String getCurrentModule() {
		String[] filename = theView.getBuffer().getName().split("\\.", 2);
		return filename[0];
	}
	
	public Invocation getCurrentInvocation() {
		int cursor = theView.getTextArea().getCaretPosition();
		String text = theView.getTextArea().getText();
		if (!validChar(text.charAt(cursor))) {
			if (cursor > 0 && validChar(text.charAt(cursor - 1))) {
				cursor--;
			} else {
				return null;
			}
		}
		int ini = cursor;
		while (ini >= 0 && validChar(text.charAt(ini))) {
			ini--;
		}
		ini++;
		int fin = cursor;
		while (fin < text.length() && validChar(text.charAt(fin))) {
			fin++;
		}
		int paramsIni = fin;
		while (paramsIni < text.length() && text.charAt(paramsIni) != '(') {
			if (!Character.isWhitespace(text.charAt(paramsIni))) return null;
			paramsIni++;
		}
		if (paramsIni > text.length()) return null;
		int arity = Parser.getArity(text, paramsIni + 1);
		if (arity < 0) return null;
		
		String[] modFun = text.substring(ini, fin).split(":", 2);
		if (modFun.length < 2) {
			return new Invocation(modFun[0], arity);
		} else {
			return new Invocation(modFun[0], modFun[1], arity);
		}
	}

	//test
	public static void main(String[] args) {
		String filename = "cmFunctions.erl";
		String workdir = "/upsdev/lib/ups_cmConnector/src_mh";
		String text = "";
		try {
			BufferedReader fr;
			fr = new BufferedReader(new FileReader(workdir + "/" + filename));
			StringBuffer buffer = new StringBuffer();
			while (fr.ready()) {
				buffer.append(fr.readLine() + "\n");
			}
			text = buffer.toString();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		List<SourceLine> ls = Parser.parse("hola", text);
		for (SourceLine s: ls) {
			System.out.println(s);
		}
	}

	public View getTheView() {
		return theView;
	}

	public void setTheView(View theView) {
		this.theView = theView;
	}

	public ErlangProjectPanel getProject() {
		return prj;
	}

	public void setProject(ErlangProjectPanel prj) {
		this.prj = prj;
	}

	public void compile() {
		String workdir = theView.getBuffer().getDirectory();
		String filename = theView.getBuffer().getName();
		String fullPath = theView.getBuffer().getPath();
		
		List<CompilerLine> lines;
		if (prj == null) {
			lines = Compiler.compile(filename, workdir);
		} else {
			lines = Compiler.compile(prj.getCompilerPath(), filename, workdir);
		}
		source.removeFileErrors(fullPath);
		for (CompilerLine line: lines) {
			source.addError(
				line.isWarning()?ErrorSource.WARNING:ErrorSource.ERROR,
				fullPath,
				line.getLineNumber() - 1,
				0,
				0,
				line.getMessage()
			);
		}
	}

	public void functionList() {
		Buffer buffer = theView.getBuffer();
		String text = buffer.getText(0, buffer.getLength());
		List<SourceLine> funs = Parser.parse(buffer.getPath(), text);
		
		final JList fl = new JList(funs.toArray());
		functionList.getViewport().setView(fl);

		MouseListener mouseListener = new MouseAdapter() {
		    public void mouseClicked(MouseEvent e) {
		        if (e.getClickCount() == 2) {
		            int index = fl.locationToIndex(e.getPoint());
		            SourceLine sl = (SourceLine)fl.getModel().getElementAt(index);
		            gotoSourceLine(sl.getLine(), sl.getSourceFile());
		         }
		    }
		};
		fl.addMouseListener(mouseListener);
	}
	
	public void findDeclaration() {
		if (prj != null) {
			info.setText(prj.findClick());
		} else {
			info.setText(findOnCurrentFile());
		}
	}
	
	public void back() {
		nav.goBack();
	}
	
	public void forth() {
		nav.goForward();
	}
	
	public void build() {
		if (prj != null) {
			prj.build();
		}
	}
	
	public Map<String, AnalysisResult> getAnalysis() {
		if (prj == null) return null;
		else return prj.getAnalysis();
	}
	
	private static ErlangTools instance;
	
	public static ErlangTools getInstance() {
		return instance;
	}
	
	private String getSelectedWord() {
		String word = theView.getTextArea().getSelectedText();
		if (word == null || word.equals("")) {
			int cursor = theView.getTextArea().getCaretPosition();
			String text = theView.getTextArea().getText();
			int ini = cursor;
			while (ini > 0 && atomChar(text.charAt(ini))) {
				ini--;
			}
			ini++;
			
			int fin = cursor;
			while (fin < text.length() && atomChar(text.charAt(fin))) {
				fin++;
			}
		
			if (ini >= fin) return null;
			word = text.substring(ini, fin);
		}
		return word;
	}
	
	public void findNext() {
		String word = getSelectedWord();
		if (word != null) {
			SearchAndReplace.setSearchString(word);
			SearchAndReplace.setReverseSearch(false);
			SearchAndReplace.find(theView);
		}
	}
	
	public void findPrevious() {
		String word = getSelectedWord();
		if (word != null) {
			SearchAndReplace.setSearchString(word);
			SearchAndReplace.setReverseSearch(true);
			SearchAndReplace.find(theView);
		}
	}

	public DefaultErrorSource getErrorSource() {
		return source;
	}
}
